package net.jgrm.multilevelcache.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.github.benmanes.caffeine.cache.Caffeine;
import net.jgrm.multilevelcache.cache.MultiLevelCacheManager;
import net.jgrm.multilevelcache.service.ThreadSafeCacheService;
import net.jgrm.multilevelcache.sync.CacheSyncListener;
import net.jgrm.multilevelcache.sync.CacheSyncPublisher;
import net.jgrm.redisadvanced.lock.RedisLockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * 多级缓存自动配置类
 * 根据配置属性自动装配Caffeine和Redis缓存
 *
 * @author jgrm
 */
@Configuration
@EnableCaching
@EnableConfigurationProperties(MultiLevelCacheProperties.class)
@ConditionalOnProperty(prefix = "multilevel.cache", name = "enabled", havingValue = "true", matchIfMissing = true)
@AutoConfigureAfter({RedisAutoConfiguration.class, CacheAutoConfiguration.class})
public class MultiLevelCacheAutoConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(MultiLevelCacheAutoConfiguration.class);

    /**
     * Caffeine本地缓存管理器
     * 仅在启用Caffeine且模式支持本地缓存时创建
     */
    @Bean("caffeineCacheManager")
    @ConditionalOnClass(com.github.benmanes.caffeine.cache.Cache.class)
    @ConditionalOnProperty(prefix = "multilevel.cache.caffeine", name = "enabled", havingValue = "true", matchIfMissing = true)
    public CaffeineCacheManager caffeineCacheManager(MultiLevelCacheProperties properties) {
        MultiLevelCacheProperties.Caffeine caffeineConfig = properties.getCaffeine();

        CaffeineCacheManager cacheManager = new CaffeineCacheManager();

        // 构建Caffeine配置（移除refreshAfterWrite）
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                .maximumSize(caffeineConfig.getMaximumSize())
                .initialCapacity(caffeineConfig.getInitialCapacity());

        // 设置过期时间（移除refreshAfterWrite）
        if (caffeineConfig.getExpireAfterWrite() != null) {
            caffeine.expireAfterWrite(caffeineConfig.getExpireAfterWrite());
        }
        if (caffeineConfig.getExpireAfterAccess() != null) {
            caffeine.expireAfterAccess(caffeineConfig.getExpireAfterAccess());
        }
        // 移除refreshAfterWrite相关代码

        // 是否记录统计信息
        if (caffeineConfig.isRecordStats()) {
            caffeine.recordStats();
        }

        cacheManager.setCaffeine(caffeine);
        cacheManager.setAllowNullValues(caffeineConfig.isAllowNullValues());

        return cacheManager;
    }


    /**
     * Redis缓存管理器
     * 仅在存在RedisConnectionFactory且启用Redis时创建
     */
    @Bean("redisCacheManager")
    @ConditionalOnClass(RedisConnectionFactory.class)
    @ConditionalOnProperty(prefix = "multilevel.cache.redis", name = "enabled", havingValue = "true", matchIfMissing = true)
    public RedisCacheManager redisCacheManager(RedisConnectionFactory factory,
                                               MultiLevelCacheProperties properties) {
        logger.info("创建Redis缓存管理器，RedisConnectionFactory: {}", factory.getClass().getSimpleName());

        MultiLevelCacheProperties.Redis redisConfig = properties.getRedis();

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

        // 设置TTL
        if (redisConfig.getDefaultTtl() != null) {
            config = config.entryTtl(redisConfig.getDefaultTtl());
            logger.debug("Redis缓存TTL设置为: {}", redisConfig.getDefaultTtl());
        }

        // 是否缓存null值
        if (!redisConfig.isCacheNullValues()) {
            config = config.disableCachingNullValues();
            logger.debug("Redis缓存禁用null值缓存");
        }

        // 设置键前缀
        if (redisConfig.isUsePrefix() && redisConfig.getKeyPrefix() != null) {
            config = config.prefixCacheNameWith(redisConfig.getKeyPrefix());
            logger.debug("Redis缓存键前缀设置为: {}", redisConfig.getKeyPrefix());
        }

        // 设置序列化器
        config = config.serializeKeysWith(
                RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));

        // 根据序列化类型设置值序列化器
        if (redisConfig.getSerializationType() == MultiLevelCacheProperties.Redis.SerializationType.JSON) {
            config = config.serializeValuesWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(createJsonRedisSerializer(redisConfig)));
            logger.debug("Redis缓存使用JSON序列化");
        }
        // JDK序列化使用默认设置

        RedisCacheManager manager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();

        logger.info("Redis缓存管理器创建完成: {}", manager.getClass().getSimpleName());
        return manager;
    }

    /**
     * 创建JSON Redis序列化器
     */
    private Jackson2JsonRedisSerializer<Object> createJsonRedisSerializer(MultiLevelCacheProperties.Redis redisConfig) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        // 根据配置决定是否启用类型信息存储
        if (redisConfig.isEnableDefaultTyping()) {
            mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        }

        // 添加JavaTimeModule以支持Java 8时间类型
        mapper.registerModule(new JavaTimeModule());

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        serializer.setObjectMapper(mapper);

        return serializer;
    }

    /**
     * 多级缓存管理器
     * 根据配置模式决定使用哪种缓存实现
     */
    @Bean("multiLevelCacheManager")
    @Primary
    @ConditionalOnMissingBean(name = "cacheManager")
    public CacheManager multiLevelCacheManager(MultiLevelCacheProperties properties,
                                               @Autowired(required = false) CaffeineCacheManager caffeineCacheManager,
                                               @Autowired(required = false) RedisCacheManager redisCacheManager) {

        logger.info("创建多级缓存管理器，模式: {}", properties.getMode());
        logger.info("Caffeine缓存管理器是否可用: {}", caffeineCacheManager != null);
        logger.info("Redis缓存管理器是否可用: {}", redisCacheManager != null);

        MultiLevelCacheProperties.CacheMode mode = properties.getMode();

        switch (mode) {
            case LOCAL_ONLY:
                if (caffeineCacheManager != null) {
                    logger.info("使用LOCAL_ONLY模式，返回Caffeine缓存管理器");
                    return caffeineCacheManager;
                }
                throw new IllegalStateException("LOCAL_ONLY模式下Caffeine缓存管理器不可用");
            case REDIS_ONLY:
                if (redisCacheManager != null) {
                    logger.info("使用REDIS_ONLY模式，返回Redis缓存管理器");
                    return redisCacheManager;
                }
                throw new IllegalStateException("REDIS_ONLY模式下Redis缓存管理器不可用");
            case MULTI_LEVEL:
            default:
                MultiLevelCacheManager multiManager = new MultiLevelCacheManager(caffeineCacheManager, redisCacheManager);
                logger.info("使用MULTI_LEVEL模式，创建多级缓存管理器: {}", multiManager.getClass().getSimpleName());
                return multiManager;
        }
    }

    /**
     * 线程安全缓存服务
     * 仅在存在RedisLockService时自动装配
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean(RedisLockService.class)
    @ConditionalOnProperty(prefix = "multilevel.cache", name = "thread-safe-enabled", havingValue = "true", matchIfMissing = true)
    public ThreadSafeCacheService threadSafeCacheService() {
        logger.info("创建线程安全缓存服务");
        return new ThreadSafeCacheService();
    }

    /**
     * 缓存同步监听器
     * 仅在启用缓存同步时创建
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "multilevel.cache", name = "sync-enabled", havingValue = "true", matchIfMissing = false)
    @ConditionalOnClass(RedisConnectionFactory.class)
    public CacheSyncListener cacheSyncListener(CacheManager cacheManager) {
        ObjectMapper mapper = createObjectMapper();
        logger.info("创建缓存同步监听器");
        return new CacheSyncListener(cacheManager, mapper);
    }

    /**
     * 缓存同步发布器
     * 仅在启用缓存同步时创建
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "multilevel.cache", name = "sync-enabled", havingValue = "true", matchIfMissing = false)
    @ConditionalOnClass(RedisConnectionFactory.class)
    @ConditionalOnBean(CacheSyncListener.class)
    public CacheSyncPublisher cacheSyncPublisher(RedisTemplate<String, Object> redisTemplate,
                                                 CacheSyncListener cacheSyncListener) {
        ObjectMapper mapper = createObjectMapper();
        logger.info("创建缓存同步发布器");
        return new CacheSyncPublisher(redisTemplate, mapper, cacheSyncListener);
    }

    /**
     * Redis消息监听容器
     * 配置缓存同步监听
     */
    @Bean
    @ConditionalOnProperty(prefix = "multilevel.cache", name = "sync-enabled", havingValue = "true", matchIfMissing = false)
    @ConditionalOnClass(RedisConnectionFactory.class)
    @ConditionalOnBean(CacheSyncListener.class)
    public RedisMessageListenerContainer redisMessageListenerContainer(
            RedisConnectionFactory connectionFactory,
            CacheSyncListener cacheSyncListener) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);

        // 订阅缓存同步频道
        container.addMessageListener(cacheSyncListener,
                new ChannelTopic(CacheSyncPublisher.CACHE_SYNC_CHANNEL));

        logger.info("配置Redis消息监听容器，订阅频道: {}", CacheSyncPublisher.CACHE_SYNC_CHANNEL);
        return container;
    }

    /**
     * 创建ObjectMapper
     */
    private ObjectMapper createObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.registerModule(new JavaTimeModule());
        return mapper;
    }
}
